﻿using System;
using System.Linq;
using Affinity.Core.Exceptions;
using Affinity.Core.Utility;
using Affinity.Synthesis.ExpressionCells.Helpers;

namespace Affinity.Synthesis.ExpressionCells.Basis
{
    /// <summary>
    /// A shim to 'convert' a function to another with fewer parameters.
    /// </summary>
    public abstract class MethodClosure
    {
        /// <summary>
        /// Factory method for creating a MethodClosure.
        /// </summary>
        /// <param name="innerDelegate"></param>
        /// <param name="innerMethodDescription"></param>
        /// <param name="closureParameters"></param>
        /// <param name="genericArguments"></param>
        /// <returns></returns>
        public static MethodClosure Create(Delegate innerDelegate, string innerMethodDescription, object[] closureParameters, params Type[] genericArguments)
        {
            switch (genericArguments.Length)
            {
                case 1: return (MethodClosure)Activator.CreateInstance(typeof(MethodClosure<>).MakeGenericType(genericArguments), innerDelegate, innerMethodDescription, closureParameters);
                case 2: return (MethodClosure)Activator.CreateInstance(typeof(MethodClosure<,>).MakeGenericType(genericArguments), innerDelegate, innerMethodDescription, closureParameters);
                case 3: return (MethodClosure)Activator.CreateInstance(typeof(MethodClosure<,,>).MakeGenericType(genericArguments), innerDelegate, innerMethodDescription, closureParameters);
                case 4: return (MethodClosure)Activator.CreateInstance(typeof(MethodClosure<,,,>).MakeGenericType(genericArguments), innerDelegate, innerMethodDescription, closureParameters);

                default: throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Constructor accepting the innerDelegate to represent, the description of the inner method, and the parameters to close over.
        /// </summary>
        /// <param name="innerDelegate"></param>
        /// <param name="innerMethodDescription"></param>
        /// <param name="closureParameters"></param>
        protected MethodClosure(Delegate innerDelegate, string innerMethodDescription, params object[] closureParameters)
        {
            _ClosureParameters = closureParameters;
            _InnerDelegateInvoker = DelegateInvoker.Create(innerDelegate);
            _InnerMethodDescription = innerMethodDescription;
        }

        /// <summary>
        /// Gets the outer delegate (the one accepting fewer arguments).
        /// </summary>
        public abstract Delegate OuterDelegate { get; }

        /// <summary>
        /// Implements the outer method (the one accepting fewer arguments).
        /// </summary>
        /// <param name="outerParameters"></param>
        /// <returns></returns>
        public object OuterMethod(params object[] outerParameters)
        {
            object[] parameterArray = new object[outerParameters.Length + _ClosureParameters.Length];
            Array.Copy(outerParameters, parameterArray, outerParameters.Length);
            Array.Copy(_ClosureParameters, 0, parameterArray, outerParameters.Length, _ClosureParameters.Length);
            return _InnerDelegateInvoker.Invoke(parameterArray);
        }

        private object[] _ClosureParameters = null;
        private DelegateInvoker _InnerDelegateInvoker = null;
        private string _InnerMethodDescription = null;
    }

    /// <summary>
    /// The closure for an outer method of type Func&lt;TResult&gt;.
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    internal class MethodClosure<TResult> : MethodClosure
    {
        public MethodClosure(Delegate innerDelegate, string innerMethodDescription, params object[] closureParameters)
            : base(innerDelegate, innerMethodDescription, closureParameters)
        {
        }

        public override Delegate OuterDelegate
        {
            get { return (Func<TResult>)OuterMethod; }
        }

        private TResult OuterMethod()
        {
            return (TResult)base.OuterMethod();
        }
    }
    /// <summary>
    /// The closure for an outer method of type Func&lt;TParam, TResult&gt;.
    /// </summary>
    /// <typeparam name="TParam"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class MethodClosure<TParam, TResult> : MethodClosure
    {
        public MethodClosure(Delegate innerDelegate, string innerMethodDescription, params object[] closureParameters)
            : base(innerDelegate, innerMethodDescription, closureParameters)
        {
        }

        public override Delegate OuterDelegate
        {
            get { return (Func<TParam, TResult>)OuterMethod; }
        }

        private TResult OuterMethod(TParam param)
        {
            return (TResult)base.OuterMethod(param);
        }
    }

    /// <summary>
    /// The closure for an outer method of type Func&lt;TParam1, TParam2, TResult&gt;.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class MethodClosure<TParam1, TParam2, TResult> : MethodClosure
    {
        public MethodClosure(Delegate innerDelegate, string innerMethodDescription, params object[] closureParameters)
            : base(innerDelegate, innerMethodDescription, closureParameters)
        {
        }

        public override Delegate OuterDelegate
        {
            get { return (Func<TParam1, TParam2, TResult>)OuterMethod; }
        }

        private TResult OuterMethod(TParam1 param1, TParam2 param2)
        {
            return (TResult)base.OuterMethod(param1, param2);
        }
    }

    /// <summary>
    /// The closure for an outer method of type Func&lt;TParam1, TParam2, TParam3, TResult&gt;.
    /// </summary>
    /// <typeparam name="TParam1"></typeparam>
    /// <typeparam name="TParam2"></typeparam>
    /// <typeparam name="TParam3"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    internal class MethodClosure<TParam1, TParam2, TParam3, TResult> : MethodClosure
    {
        public MethodClosure(Delegate innerDelegate, string innerMethodDescription, params object[] closureParameters)
            : base(innerDelegate, innerMethodDescription, closureParameters)
        {
        }

        public override Delegate OuterDelegate
        {
            get { return (Func<TParam1, TParam2, TParam3, TResult>)OuterMethod; }
        }

        private TResult OuterMethod(TParam1 param1, TParam2 param2, TParam3 param3)
        {
            return (TResult)base.OuterMethod(param1, param2, param3);
        }
    }
    
    /*
    public class MethodClosure
    {
        public MethodClosure(Delegate innerDelegate, params Func<object>[] closureMethods)
        {
            _ClosureMethods = closureMethods;
            _InnerDelegate = innerDelegate;
            _OuterDelegate = OuterMethod;
        }

        public delegate object OuterDelegateType(params object[] outerparams);

        public OuterDelegateType OuterDelegate
        {
            get { return _OuterDelegate; }
        }

        private object OuterMethod(params object[] outerParameters)
        {
            return _InnerDelegate.DynamicInvoke(outerParameters, _ClosureMethods.Select(closureMethod => closureMethod()));
        }

        private Func<object>[] _ClosureMethods = null;
        private Delegate _InnerDelegate = null;
        private OuterDelegateType _OuterDelegate = null;
    }
    */
}
